using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MvpFramework.DesignByContract;
using MvpFramework.Infastructure;
using MvpFramework.Specifications;

namespace MvpFramework
{
    /// <summary>
    /// Object that does Validation for Presenters
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class PresenterValidator<T> where T : IMvpView
    {
        private readonly Presenter<T> _presenter;
        private SpecificationManager _specificationManager;
        private ReadOnlyCollection<Specification<T>> failedSpecs;
        private ReadOnlyCollection<Specification<T>> passedSpecs;
        private bool isValid;
        private bool viewNeedsValidation = true;
        private List<IValidationMessage> visibleErrors;
        private List<IValidationMessage> passed;

        public PresenterValidator(Presenter<T> presenter)
        {
            _presenter = presenter;
        }

        private IValidatedView View
        {
            get
            {
                CheckViewRequirementsForValidation();
                return (IValidatedView) _presenter.View;
            }
        }

        /// <summary>
        /// The <see cref="Specification{T}"/> used by the Presenter
        /// </summary>
        internal SpecificationManager SpecificationManager
        {
            get
            {
                if (_specificationManager == null)
                {
                    _specificationManager = new SpecificationManager();
                }
                return _specificationManager;
            }
            set { _specificationManager = value; }
        }

        /// <summary>
        /// Checks if the View is Valid.
        /// </summary>
        protected internal virtual bool IsValid
        {
            get
            {
                if (!viewNeedsValidation) return isValid;
                CheckViewRequirementsForValidation();
                Validate();
                return isValid;
            }
            set { isValid = value; }
        }

        /// <summary>
        /// The Function to Execute to determine if View is Valid. 
        /// </summary>
        protected internal virtual Func<bool> ViewIsValidFunction
        {
            get { return () => GetFailedSpecifications().Count == 0; }
        }

        /// <summary>
        /// Validates the View and sets the IsValid property. 
        /// will be set. 
        /// </summary>
        /// <returns></returns>
        protected internal void Validate()
        {
            CheckViewRequirementsForValidation();
            GetFailedSpecifications();
            GetPassedSpecifications();
            IsValid = ViewIsValidFunction.Invoke();
            SetValidationLabelsVisibility();
            viewNeedsValidation = false;
        }

        /// <summary>
        /// Returns the Failed <see cref="Specification{T}"/> for the view.
        /// </summary>
        /// <returns></returns>
        protected virtual ReadOnlyCollection<Specification<T>> GetFailedSpecifications()
        {
            failedSpecs = SpecificationManager.GetFailedSpecifications(_presenter.View);
            return failedSpecs;
        }

        protected virtual ReadOnlyCollection<Specification<T>> GetPassedSpecifications()
        {
            passedSpecs = SpecificationManager.GetPassedSpecifications(_presenter.View);
            return passedSpecs;
        }

        private void SetValidationLabelsVisibility()
        {
            SetVisibleErrors();
            SetPassed();
            if (View.ValidationErrorLabels == null)
            {
                return;
            }
            foreach (IValidationMessage label in View.ValidationErrorLabels)
            {
                SetLabelVisibility(label, visibleErrors);
            }
            foreach (IValidationMessage label in View.ValidationErrorLabels)
            {
                DisableVisibility(label, passed);
            }
        }

        private void DisableVisibility(IValidationMessage validationMessage, List<IValidationMessage> list)
        {
            if (validationMessage == null) return;
            validationMessage.Visible = !list.Contains(validationMessage); ;
        }

        private void SetLabelVisibility(IValidationMessage validationMessage, List<IValidationMessage> list)
        {
            if (validationMessage == null) return;
            validationMessage.Visible = list.Contains(validationMessage);
        }

        private void SetVisibleErrors()
        {
            visibleErrors = new List<IValidationMessage>();
            foreach (var spec in failedSpecs)
            {
                visibleErrors.Add(View.GetValidationLabelFor(spec));
            }
        }

        private void SetPassed()
        {
            passed = new List<IValidationMessage>();
            foreach (var spec in passedSpecs)
            {
                passed.Add(View.GetValidationLabelFor(spec));
            }
        }

        private void CheckViewRequirementsForValidation()
        {
            Check.Require(_presenter.View is IValidatedView, "Can not call Validate when View is not an IValidatedView");
        }
    }
}